package com.mattgiant.corP5e;

import java.io.File;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PGraphics;
import processing.core.PImage;
import processing.core.PVector;

public class CorP5eSession extends CorP5eConstants
{
	private static native void initSession();
	private static native void shutdownSession();

	private final PApplet applet;
	
	public CorP5eSession( PApplet applet )
	{
		System.loadLibrary( "CorP5e" );
		initSession();
		this.applet = applet;
	}
	
	public void close()
	{
		shutdownSession();
	}

	@Override
	protected void finalize() throws Throwable
	{
		close();
	}
	/** create a new filter object with filterName **/
	public CorP5eFilter newFilter( String filterName )
	{
		return new CIFilter( filterName );
	}
	
	/** create a new Movie with width and height **/
	public CorP5eMovie newMovie(	String 	name,
									int 	width, 
									int 	height )
	{
		return new QTMovie( applet.sketchPath + File.separator + name,
							width, 
							height );
	}
	
	/** create a new Movie with width, height, and frameRate **/
	public CorP5eMovie newMovie( 	String 	name,
									int 	width, 
									int 	height,
									float 	fps )
	{
		return new QTMovie( name,
							width,
							height,
							fps );
	}
	
	/** create a new Movie with width, height, and frameRate **/
	public CorP5eMovie newMovie( 	String 	name,
									int		width, 
									int		height,
									float	fps,
									String	codec,
									float	quality )
	{
		return new QTMovie( name,	
							width,
							height,
							fps,
							codec,
							quality );
	}

	
	
	/** CIFilter provides access to Core Image CIFilter class via JNI **/
	private class CIFilter implements CorP5eFilter 
	{
		/** calls jni library to allocate a new filter **/
		private native long alloc( String filterName ); 
		
		private native void dealloc();
		
		/** passes image data to jni library to be processed by filter **/
		private native int setImageValueString(	long id,
												String keyName,
												int pixelformat,
												int width,
												int height,
												int [] pixels );
		
		/** passes image data to jni library to be processed by filter **/
		private native int setImageValueInt(	long id,
												int keyName,
												int pixelformat,
												int width,
												int height,
												int [] pixels );

		private native int setFloatValueString(	long 	id,
												String 	keyName,
												float 	value );

		private native int setFloatValueInt(	long 	id,
												int 	keyName,
												float 	value );

		@Override
		protected void finalize() throws Throwable 
		{
			dealloc();
		}

		private native int setVectorValueString( 	long 	id,
												 	String 	key,
												 	float 	x,
												 	float 	y,
												 	float 	z,
												 	float 	w );	
		
		private native int setVectorValueInt( 		long 	id,
											    	int 	key,
											    	float 	x,
											    	float 	y,
											    	float 	z,
											    	float 	w );	
		
		private native int setColorValueString( 	long 	id,
													String 	key,
													float 	r,
													float 	g,
													float	b,
													float 	a );

		private native int setColorValueInt( 		long 	id,
													int 	key,
													float	r,
													float	g,
													float	b,
													float 	a );
		
		private native int getImageForKey(	long 	filterId,
											String 	key,
											PImage 	outImage,
											boolean preserveSize );
		
		/** filter id for filter created by jni library returned via alloc() method **/
		private final long filterId;
		
		/** creates a Core Image filter using the provided filterName parameter **/
		public CIFilter( String filterName )
		{
			filterId = alloc( filterName );	
					//call native method to instantiate actual filter
			
			//check success of native method
			if ( filterId == -1 )
			{
				throw new RuntimeException( String.format( "Could not instantiate CIFilter named %s", filterName ) );
			}
		}
		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#setValueForKey(java.lang.String, processing.core.PImage)
		 */
		@Override
		public void setValueForKey( String key,
								  	PImage value )
		{
			int res = setImageValueString(	filterId,
											key,
											value.format, 
											value.width,
											value.height, 
											value.pixels );
			
			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set image parameter." );
			}
		}
		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#setValueForKey(int, processing.core.PImage)
		 */
		@Override
		public void setValueForKey( int key,
								  	PImage value )
		{
			int res = setImageValueInt(	filterId,
										key,
										value.format, 
										value.width,
										value.height, 
										value.pixels );
			
			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set image parameter." );
			}
		}
		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#setValueForKey(java.lang.String, float)
		 */
		@Override
		public void setValueForKey( String 	key,
								  	float 	value )
		{
			int res = setFloatValueString( 	filterId,
											key,
											value );	
		
			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set float parameter." );
			}
		}
		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#setValueForKey(int, float)
		 */
		@Override
		public void setValueForKey( int		key,
								  	float	value )
		{
			int res = setFloatValueInt( 	filterId,
											key,
											value );	
		
			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set float parameter." );
			}
		}
		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#setValueForKey(java.lang.String, float, float, float, float)
		 */
		@Override
		public void setVectorForKey(	String 	key,
								  		float 	x,
								  		float   y,
								  		float 	z,
								  		float 	w )
		{
			int res = setVectorValueString( filterId,
											key,
											x,
											y,
											z,
											w );	
		
			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set float parameter." );
			}
		}
		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#setValueForKey(int, float, float, float, float)
		 */
		@Override
		public void setVectorForKey(	int	 	key,
								  		float	x,
								  		float	y,
								  		float	z,
								  		float	w )
		{
			int res = setVectorValueInt( 	filterId,
											key,
											x,
											y,
											z,
											w );	
		
			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set float parameter." );
			}
		}
		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#setValueForKey(java.lang.String, float, float, float, float)
		 */
		@Override
		public void setColorForKey(	String 	key,
								  	float 	r,
								  	float   g,
								  	float 	b,
								  	float 	a )
		{
			PGraphics pg = applet.g;
			
			int res = setColorValueString( 	filterId,
											key,
											r / pg.colorModeX,
											g / pg.colorModeY,
											b / pg.colorModeZ,
											a / pg.colorModeA );
				//divisions scale Processing domain colors into CoreImage [0-1] colors
		
			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set float parameter." );
			}
		}
		
		@Override
		public void setColorForKey(	int	 	key,
		  							float	r,
		  							float	g,
		  							float	b,
		  							float	a )
		{
			PGraphics pg = applet.g;
			
			int res = setColorValueInt( 	filterId,
											key,
											r / pg.colorModeX,
											g / pg.colorModeY,
											b / pg.colorModeZ,
											a / pg.colorModeA );
				//divisions scale Processing domain colors into CoreImage [0-1] colors
			

			if ( res < 0 )
			{
				throw new IllegalArgumentException( "Could not set float parameter." );
			}
		}

		
		/* (non-Javadoc)
		 * @see com.mattgiant.corP5e.CorP5eFilter#getImageOutputValue(java.lang.String)
		 */
		@Override
		public PImage getImageOutputValue( String key )
		{
			PImage outImg = new PImage();
			outImg.format = PConstants.ARGB;
			
			int res = getImageForKey(	filterId, 
										key, 
										outImg, 
										false );
			
			if ( res < 0 )
			{
				throw new IllegalArgumentException( String.format( "Could not get image for key %s", key ) );
			}
			
			return outImg;
		}
		
		/**
		 * Process the main applet display
		 * @see com.mattgiant.corP5e.CorP5eFilter#process(processing.core.PApplet)
		 **/
		@Override
		public void process()
		{
			//make sure pixels are available
			applet.g.loadPixels();
			
			setValueForKey( CorP5eConstants.kCIInputImageKey, 
							applet.g );
			
			getImageForKey( filterId, 
							"outputImage", 
							applet.g,
							true );
			
			//refresh display with processed new pixels
			applet.g.updatePixels();
		}
		
		/** 
		 * process a PImage passed in and return a new PImage.  Output resolution
		 * and formats may not match input
		 * 
		 */
		@Override
		public PImage process( PImage input )
		{
			PImage newImg = new PImage();
			
			setValueForKey( CorP5eConstants.kCIInputImageKey, input );
			getImageForKey(filterId, "outputImage", newImg, false );
			
			return newImg;
		}

		@Override
		public void setVectorForKey(	String 	key, 
										float 	x, 
										float 	y,
										float 	z )
		{
			setVectorForKey( key, x, y, z, 0.0f );
		}

		@Override
		public void setVectorForKey(	String 	key, 
										float 	x, 
										float 	y ) 
		{
			setVectorForKey( key, x, y, 0.0f, 0.0f );
		}

		@Override
		public void setVectorForKey(	int	 	key, 
										float 	x, 
										float 	y, 
										float 	z )
		{
			setVectorForKey( key, x, y, z, 0.0f );
		}

		@Override
		public void setVectorForKey(	int 	key, 
										float 	x, 
										float 	y )
		{
			setVectorForKey( key, x, y, 0.0f, 0.0f );
		}

		@Override
		public void setVectorForKey( int key, PVector vector )
		{
			setVectorForKey( 	key, 
								vector.x,
								vector.y,
								vector.z,
								0.0f );
		}

		@Override
		public void setVectorForKey( String key, PVector vector )
		{
			setVectorForKey( 	key, 
								vector.x, 
								vector.y,
								vector.z,
								0.0f );
		}

		@Override
		public void setColorForKey(	int key, 
									int r, 
									int g, 
									int b, 
									int a) 
		{
			setColorForKey( key, (float)r, (float)g, (float)b, (float)a );
		}

		@Override
		public void setColorForKey(	String key, 
									int r, 
									int g, 
									int b, 
									int a ) 
		{
			setColorForKey( key, (float)r, (float)g, (float)b, (float)a );
		}
	}
	
	/** implementation of CorP5eMovie interface, implemented here to allow 
	 * free access to session **/
	private class QTMovie implements CorP5eMovie
	{
		private native long alloc0(	String name,
									int width,
									int height );
		
		private native long alloc1(	String	name,
									int 	width,
									int 	height,
									float 	frameRate );
		
		private native long alloc2(	String 	name,
									int		width,
									int		height,
									float	frameRate,
									String	codec,
									float	quality );
		
		private native void addFrame( 	long	movieId,
										int		pixelFormat,
										int		width,
										int		height,
										int []	pixels );
		
		private native void dealloc( long id );
		
		private native void writeMovie(	long	movieId );
		
		private final long movieId;

		public QTMovie(	String 	name,
						int 	width, 
						int 	height) 
		{
			movieId = alloc0( 	name,
								width, 
								height );
		}

		public QTMovie(	String 	name,
						int 	width, 
						int 	height, 
						float 	fps ) 
		{
			movieId = alloc1( 	name,
								width, 
								height, 
								fps );
		}

		public QTMovie(	String 	name,
						int 	width, 
						int		height, 
						float	fps, 
						String	codec,
						float	quality )
		{
			movieId = alloc2( 	name,
								width,
								height,
								fps,
								codec,
								quality );
			
		}

		@Override
		protected void finalize() throws Throwable 
		{
			dealloc( movieId );
				//call native dealloc
		}

		/** add the provided image to movie **/
		public void addFrame( PImage img )
		{
			addFrame( 	movieId, 
						img.format,
						img.width,
						img.height,
						img.pixels );
			
		}

		/**add current Processing display to movie **/
		public void addFrame() 
		{
			//make sure pixels are available
			applet.g.loadPixels();
			
			addFrame( applet.g );
		}

		/** finish writing video to disk **/
		public void writeFile()
		{
			writeMovie( movieId );
		}
	}
}
